﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Management;

namespace UsbGuard.Core
{
    public class Intelligence
    {
        private System.Threading.Thread usbWatcher;
        private System.Threading.Thread usbDeviceWatcher;
        private int MD5count = 3;
        private UsbGuard_DatabaseDataContext dataContext;        
        private List<USBDeviceInfo> sticksHandled = new List<USBDeviceInfo>();
        private List<USBDeviceInfo> sticksConnected = new List<USBDeviceInfo>();
        private List<KeyValuePair<String, String>> sticksCached = new List<KeyValuePair<String, String>>(); 
        private bool keepWatching = true;
        private int bueckdichhoch = 2000;
        public delegate void UnregisteredStickRecognizedHandler();
        public delegate void MasterStickRecognizedHandler(object sender, Int32 StickID);
        public event MasterStickRecognizedHandler MasterStickRecognized;
        public event UnregisteredStickRecognizedHandler UnregisteredStickRecognized;
        public event EventHandler NewStickRecognized;
        private System.Data.IDbConnection connection;
        private const String _DOMAIN = "Marco-PC2";
        private String startUpPath;
        private const String dbName = "data.sdf";

        /// <summary>
        /// Gibt an, ob Klausurmodus ist.
        /// </summary>
        public Boolean IsExamMode { get { return !this.NetworkUp; } }

        /// <summary>
        /// Initialisiert die Intelligenz des Frameworks.
        /// </summary>
        /// <param name="Connection">Die Verbindung zur Datenbank, die die registrierten Sticks beinhaltet.</param>
        /// <param name="isAdmin">Gibt an, ob die Instanz als Administratortool gestartet wird.</param>
        public Intelligence(System.Data.IDbConnection Connection, String StartUpPath, bool isAdmin = false)
        {
            if (Connection == null)
            {
                try
                {
                    dataContext = new UsbGuard_DatabaseDataContext("Data Source=" + StartUpPath + "\\" + dbName + ";Max Database Size=256;Persist Security Info=False;");
                    startUpPath = StartUpPath;

                    try
                    {
                        dataContext.CreateDatabase();
                    }
                    catch
                    {

                    }
                    
                    this.connection = dataContext.Connection != null ? dataContext.Connection : null;
                }
                catch { }
            }
            else 
            {
                this.connection = Connection;
                dataContext = new UsbGuard_DatabaseDataContext(connection);                
            }
        }

        /// <summary>
        /// Stoppt alle Threads, die das abhören auf USB-Sticks jeglicher Art garantieren.
        /// </summary>
        public void Stop()
        {
            keepWatching = false;
            System.Threading.Thread.Sleep(bueckdichhoch + 10);
            dataContext.Connection.Close();
        }

        /// <summary>
        /// Gibt eine Liste mit registrierten Sticks zurück.
        /// </summary>
        public List<RegisteredSticks> RegisteredSticks
        {
            get
            {
                List<RegisteredSticks> regged = new List<RegisteredSticks>();
                try
                {
                    foreach (RegisteredSticks reg in dataContext.RegisteredSticks)
                    {
                        regged.Add(reg);

                    }
                }
                catch
                { }
                return regged;
            } 
        }

        /// <summary>
        /// Registriert einen UsbStick.
        /// </summary>
        /// <param name="VendorId">Die zum Stick-Passende Vendor-ID</param>
        /// <param name="Description">Eine benutzerdefinierte Beschreibung.</param>
        public void RegisterUsbStick(string VendorId, string Description, bool IsMasterStick = false, bool update = false)
        {
            RegisteredSticks Stick = null;
            if (!update)
            {
                Boolean CanRegisterStick = false;
                

                CanRegisterStick = (dataContext.RegisteredSticks.Count() > 0) ? false : true;

                if (dataContext.RegisteredSticks.Count() > 0) CanRegisterStick = (dataContext.RegisteredSticks.Where(x => x.VendorID == getMD5Hash(VendorId, MD5count)).Count() > 0) ? false : true;

                if (CanRegisterStick)
                {
                    Stick = new RegisteredSticks();
                    Stick.Description = Description;
                    Stick.VendorID = getMD5Hash(VendorId, MD5count);
                    Stick.IsMasterStick = IsMasterStick;
                    Stick.RegisterDate = DateTime.Now.ToShortDateString();
                    dataContext.RegisteredSticks.InsertOnSubmit(Stick);
                    dataContext.SubmitChanges();
                }
                else
                {
                    throw new ArgumentException("Registrieren des Sticks mit diesen Daten nicht möglich");
                }
            }
            if ((update && IsMasterStick) || (IsMasterStick && !update))
            {
                try
                {
                    prepareMasterStick(Convert.ToInt32(Stick.ID));
                }               
                catch(Exception ex)
                {
                    throw new Exception("MarcoFailedException! Please call the BusinessLogicMan!", ex);
                }
            }
           
        }

        private string getPartitionMountPointForDeviceId(string VendorId)
        {
            foreach(ManagementObject drive in new ManagementObjectSearcher("select * from Win32_DiskDrive where InterfaceType='USB'").Get())
            {
                foreach(ManagementObject partition in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + drive["DeviceID"] + "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get())
                {                  
                    foreach(ManagementObject disk in new ManagementObjectSearcher("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + partition["DeviceID"]  + "'} WHERE AssocClass = Win32_LogicalDiskToPartition").Get())
                    {
                        String vol = (string)disk["Name"];
                        return vol;
                    }
                }
            }
            throw new Exception("Gerät wurde nicht gefunden oder ist nicht in das System eingebunden (Vergessen zu mounten? Kursfristig abgezogen?)");
        }

        /// <summary>
        /// Hebt die Registrierung eines bestimmten Sticks wieder auf.
        /// </summary>
        /// <param name="Id"></param>
        public void DeregisterUsbStick(int Id)
        {
            try
            {
                if (dataContext.RegisteredSticks.Where(x => x.ID == Id).Count() > 0)
                {
                    foreach (RegisteredSticks stick in dataContext.RegisteredSticks.Where(x => x.ID == Id))
                    {
                        dataContext.RegisteredSticks.DeleteOnSubmit(stick);
                    }
                    dataContext.SubmitChanges();
                }
                else
                {
                    throw new ArgumentException("Dieser Stick wurde nicht registriert.");
                }
            }
            catch(Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Gibt an, ob das Netzwerk aktiv und bereit zum Senden von Daten ist.
        /// </summary>
        public Boolean NetworkUp
        {
            get
            {
                return false;
                IEnumerable<System.Net.NetworkInformation.NetworkInterface> nis = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces().Where(x => x.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up 
                                                                                                                                                                   && (Enum.GetName(typeof(System.Net.NetworkInformation.NetworkInterfaceType), x.NetworkInterfaceType).ToLower().Contains("ethernet")
                                                                                                                                                                   ||  Enum.GetName(typeof(System.Net.NetworkInformation.NetworkInterfaceType), x.NetworkInterfaceType).ToLower().Contains("wireless")));
                if (nis == null)
                    return false;
                bool networkIsUp = (nis.Select(x => new System.Net.NetworkInformation.Ping().Send(x.GetIPProperties().GatewayAddresses.FirstOrDefault().Address)).Where(x => x.Status == System.Net.NetworkInformation.IPStatus.Success).Count() > 0);               
                return networkIsUp;
            }           
        }

        /// <summary>
        /// Gibt eine Liste mit den aktuell angeschlossenen USB-Sticks zurück.
        /// </summary>
        public List<USBDeviceInfo> ConnectedUSBDevices
        {
            get
            {
                List<USBDeviceInfo> devices = new List<USBDeviceInfo>();

                ManagementObjectCollection collection;
                using (var searcher = new ManagementObjectSearcher(@"select DeviceID, PNPDeviceID from Win32_DiskDrive where InterfaceType='USB'"))
                    collection = searcher.Get();

                foreach (var device in collection)
                {
                    devices.Add(new USBDeviceInfo(
                    (string)device.GetPropertyValue("DeviceID"),
                    (string)device.GetPropertyValue("PNPDeviceID"),
                    ""
                    ));
                }

                collection.Dispose();
                if (devices == null) return new List<USBDeviceInfo>();
                return devices;
            }
        }
  
        /// <summary>
        /// Hält Ausschau nach unautorisierten Sticks.
        /// </summary>
        private void watchUsbSticks()
        {           
            while (keepWatching)
            {
                if (IsExamMode)
                {
                     System.Threading.Thread.Sleep(bueckdichhoch);
                    
                         foreach (USBDeviceInfo info in ConnectedUSBDevices)
                         {                             
                             if (dataContext.RegisteredSticks.Count() > 0)
                             {
                                 if (dataContext.RegisteredSticks.Where(x => x.VendorID == fetchMD5Hash(info.PnpDeviceID)).Count() == 0 && UnregisteredStickRecognized != null && sticksHandled.Where(x => info.PnpDeviceID == x.PnpDeviceID).Count() == 0)
                                 {
                                     sticksHandled.Add(info);
                                     UnregisteredStickRecognized();                                     
                                 }
                                 
                             }
                             else if (UnregisteredStickRecognized != null && sticksHandled.Where(x => info.PnpDeviceID == x.PnpDeviceID).Count() == 0)
                             {
                                 sticksHandled.Add(info);
                                 UnregisteredStickRecognized();
                             }
                             IEnumerable<UsbGuard.RegisteredSticks> sticks = dataContext.RegisteredSticks.Where(x => x.VendorID == fetchMD5Hash(info.PnpDeviceID) && x.IsMasterStick == true);
                             if (sticks.Count() == 1 && MasterStickRecognized != null && sticksHandled.Where(x => info.PnpDeviceID == x.PnpDeviceID).Count() == 0)
                             {
                                 MasterStickRecognized(this, Convert.ToInt32(sticks.FirstOrDefault().ID));
                                 sticksHandled.Add(info);
                             }
                         }
                         System.Threading.Thread.Sleep(bueckdichhoch);
                     }
                
                System.Threading.Thread.Sleep(bueckdichhoch);
            }
        }


        private string fetchMD5Hash(string p)
        {
            if (sticksCached.Where(x => x.Key == p).Count() > 0)
            {
                return sticksCached.Where(x => x.Key == p).FirstOrDefault().Value;
            }
            else
            {
                string h = getMD5Hash(p, MD5count);
                sticksCached.Add(new KeyValuePair<string, string>(p, h));
                return h;
            }
        }

        public void prepareMe(int ID)
        {
            if (this.dataContext.RegisteredSticks.Where(x => x.ID == ID && x.IsMasterStick == false).Count() > 0) throw new Exception("Dieser Stick ist kein MasterStick.");
           
            String Path = getPartitionMountPointForDeviceId(getDecryptedDeviceIDForID(ID));           
            String destination = startUpPath + "\\" + dbName;
            String source = Path + "\\" + dbName;
            double diff = (new System.IO.FileInfo(destination).LastWriteTime.Subtract(new System.IO.FileInfo(source).LastWriteTime)).TotalMilliseconds;
            if (diff < 0)
            {
                Stop();
                if (System.IO.File.Exists(destination)) System.IO.File.Delete(destination);
                System.IO.File.Copy(source, destination, true);
                Start();
            }
        }
        public void prepareMasterStick(int ID)
        {
            if (this.dataContext.RegisteredSticks.Where(x => x.ID == ID && x.IsMasterStick == false).Count() > 0) throw new Exception("Dieser Stick ist kein MasterStick.");

            String Path = getPartitionMountPointForDeviceId(getDecryptedDeviceIDForID(ID));
            
            String source = startUpPath + "\\" + dbName;
            String destination = Path + "\\" + dbName;
            if (System.IO.File.Exists(destination)) System.IO.File.Delete(destination);
            System.IO.File.Copy(source, destination, true);
        }

        private string getDecryptedDeviceIDForID(int ID)
        {
            foreach (USBDeviceInfo info in ConnectedUSBDevices)
            {
                if (dataContext.RegisteredSticks.Count() > 0)
                {
                    RegisteredSticks stick = dataContext.RegisteredSticks.Where(x => x.VendorID == fetchMD5Hash(info.PnpDeviceID)).FirstOrDefault();
                    if (stick == null) throw new Exception("Dieser Stick ist kein MasterStick.");
                    return info.PnpDeviceID;
                }
            }
            throw new Exception("Stop! Das Gerät wurde abgezogen!");
        }

        /// <summary>
        /// Gibt einen MD5 Hash als String zurück
        /// </summary>
        /// <param name="TextToHash">string der Gehasht werden soll.</param>
        /// <returns>Hash als string.</returns>
        public static string getMD5Hash(string TextToHash, int howoften)
        {
            if (howoften == 0) return TextToHash;

            //Prüfen ob Daten übergeben wurden.
            if ((TextToHash == null) || (TextToHash.Length == 0))
            {
                return string.Empty;
            }

            //MD5 Hash aus dem String berechnen. Dazu muss der string in ein Byte[]
            //zerlegt werden. Danach muss das Resultat wieder zurück in ein string.
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] textToHash = Encoding.Default.GetBytes(TextToHash);
            byte[] result = md5.ComputeHash(textToHash);
            
            return getMD5Hash(System.BitConverter.ToString(result), howoften - 1);
        }
        private void watchForNewUsbSticks()
        {
            while (keepWatching)
            {

                if (!CompareStickLists(sticksConnected, ConnectedUSBDevices))
                {
                    List<USBDeviceInfo> oldlist = new List<USBDeviceInfo>();
                    foreach (USBDeviceInfo info in sticksConnected) oldlist.Add(info);
                    if (sticksConnected.Count() > ConnectedUSBDevices.Count()) sticksConnected.Clear();

                    sticksConnected = sticksConnected.Union(ConnectedUSBDevices).Distinct().ToList();
                    foreach (USBDeviceInfo info in oldlist) if (sticksConnected.Where(x => x.PnpDeviceID == info.PnpDeviceID).Count() == 0) StickPulledOff(info.PnpDeviceID);
                    if (NewStickRecognized != null)
                    {
                        NewStickRecognized(null, null);
                    }
                }
                System.Threading.Thread.Sleep(bueckdichhoch);
            }
        }

        private void StickPulledOff(String DeviceID)
        {
            sticksHandled.RemoveAll(x => x.PnpDeviceID == DeviceID);            
        }

        private bool CompareStickLists(List<USBDeviceInfo> sticksConnected, List<USBDeviceInfo> ConnectedUSBDevices)
        {
            if (sticksConnected.Count() != ConnectedUSBDevices.Count()) return false;
            for (int i = 0; i < sticksConnected.Count(); i++)
                if (ConnectedUSBDevices.Where(x => x.PnpDeviceID == sticksConnected[i].PnpDeviceID).Count() == 0) 
                    return false;
            
            return true;
        }
        

        public void Start()
        {
            if (connection != null)
            {
                keepWatching = true;
                usbWatcher = new System.Threading.Thread(watchUsbSticks);
                usbWatcher.Start();

                usbDeviceWatcher = new System.Threading.Thread(watchForNewUsbSticks);
                usbDeviceWatcher.Start();
                if (!(dataContext.Connection.State == System.Data.ConnectionState.Open 
                    || dataContext.Connection.State == System.Data.ConnectionState.Fetching                    
                    || dataContext.Connection.State == System.Data.ConnectionState.Connecting
                    || dataContext.Connection.State == System.Data.ConnectionState.Executing)) dataContext = new UsbGuard_DatabaseDataContext(this.connection);
                
            }
        }
    }
}
